﻿using OllamaSharp;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows;

namespace MAIModel.ViewModels
{
    /// <summary>
    /// 0、Current class:
    /// 1、Realize ：
    /// 2、Current scheme：
    /// 3、Why it is better than other schemes：
    /// 4、Technical standard / dependency：
    /// 5、Reference：
    /// </summary>
    public class ShareOllamaObject
    {
        #region Field | Property | Collection | Command

        #region Field
        private bool _ollamaEnabled = false;        //ollama connected state
        private string _ollamaAppPath;              //ollama app path.
        private int recordIndex = 0;                //current record index.
        private string _currentPath;                //current record;

        private Chat chat;                          //build interactive chat model object.
        private OllamaApiClient _ollama;            //OllamaAPI object.
        #endregion

        #region Property
        public string OllamaAppPath
        {
            get { return _ollamaAppPath; }
            set { _ollamaAppPath = value; }
        }
        public bool OllamaEnabled
        {
            get { return _ollamaEnabled; }
            set { _ollamaEnabled = value; }
        }
        public OllamaApiClient Ollama
        {
            get { return _ollama; }
            set { _ollama = value; }
        }
        public Chat Chat
        {
            get { return chat; }
            set { chat = value; }
        }
        public string CurrentPath
        {
            get => _currentPath;
        }
        public int RecordIndex
        {
            get => recordIndex;
            set
            {
                recordIndex = value;
                _currentPath = $"{Environment.CurrentDirectory}//Data//{DateTime.Today.ToString("yyyyMMdd")}" +
                               $"//{DateTime.Today.ToString("yyyyMMdd")}_{recordIndex}.txt";
            }
        }
        #endregion

        #region Collection
        public ObservableCollection<string> ModelList { get; set; }
        #endregion

        #endregion

        #region Constructor
        public ShareOllamaObject()
        {
            RecordIndex = 0;
            WriteDataToFileAsync("");
            Init(OllamaAppPath, "llama3.2:9b");
        }
        #endregion

        #region other method
        /// <summary>
        /// initialite method
        /// </summary>
        private void Init(string appPath,string modelName)
        {
            OllamaAppPath =appPath;
            try
            {
                // 设置默认设备为GPU
                Environment.SetEnvironmentVariable("OLLAMA_DEFAULT_DEVICE", "gpu");
                //判断路径是否存在
                if (OllamaAppPath == string.Empty || OllamaAppPath == null) OllamaAppPath = @"ollama app.exe";
                //路径存在获取应用名
                if (File.Exists(OllamaAppPath)) OllamaAppPath = Path.GetFileName(OllamaAppPath);
                //获取环境Ollama环境变量：用于找到 ：ollama app.exe
                var filePath = FindExeInPath(OllamaAppPath);
                //如果路径存在，启动Ollama
                if (File.Exists(filePath)) CheckStartProcess(OllamaAppPath);
                //连接Ollama，并设置初始模型
                _ollama = new OllamaApiClient(new Uri("http://localhost:11434"));
                //获取本地可用的模型列表
                ModelList = (ObservableCollection<string>)GetModelList();
                var tmepModelName = ModelList.FirstOrDefault(name => name.ToLower().Contains("llama3.2"));
                if (tmepModelName!=null) _ollama.SelectedModel = tmepModelName;
                else if (ModelList.Count>0) _ollama.SelectedModel = ModelList[ModelList.Count-1];

                if (ModelList.FirstOrDefault(name => name.Equals(modelName))!=null) _ollama.SelectedModel = modelName;

                //Ollama服务启用成功
                OllamaEnabled = true;
            }
            catch (Exception)
            {
                OllamaEnabled = false;
            }
        }
        /// <summary>
        /// update the model selected by  Ollama
        /// </summary>
        public void UpdataSelectedModel(string model)
        {
            Ollama.SelectedModel = model;
            OllamaEnabled = true;
        }

        /// <summary>
        /// Start Ollama app and relevant server.
        /// </summary>
        public async void StartOllama(string appPath,string modelName)
        {
            Init(appPath,modelName); await Task.Delay(1);
        }
        /// <summary>
        /// get model list
        /// </summary>
        public Collection<string> GetModelList()
        {
            var models = _ollama.ListLocalModelsAsync();
            var modelList = new ObservableCollection<string>();
            foreach (var model in models.Result)
            {
                modelList.Add(model.Name);
            }
            return modelList;
        }
        #endregion

        #region starting or closeing method of Ollama(server).
        /// <summary>
        /// Finds whether the specified application name is configured in the system environment. 
        /// If it exists, return the full path, otherwise return null
        /// </summary>
        public static string FindExeInPath(string exeName)
        {
            // 获取环境变量 PATH 的值
            var pathVariable = Environment.GetEnvironmentVariable("PATH");

            // 将 PATH 分割成多个路径
            string[] paths = pathVariable.Split(Path.PathSeparator);

            // 遍历每个路径，检查是否存在指定的 .exe 文件
            foreach (string path in paths)
            {
                string fullPath = Path.Combine(path, exeName);
                if (File.Exists(fullPath))
                {
                    return fullPath;
                }
            }
            return null;
        }

        /// <summary>
        ///Startup program Specifies a program, enters a program name, and determines whether the program is running.
        ///     If it is running, exit directly, otherwise run the program according to the input path.
        /// </summary>
        public static void CheckStartProcess(string processPath)
        {
            string processName = Path.GetFileName(processPath);
            CheckStartProcess(processName, processPath);
        }

        /// <summary>
        /// Startup program Specifies a program, enters a program name, and determines whether the program is running. 
        ///     If it is running, exit directly, otherwise run the program according to the input path.
        /// </summary>
        public static void CheckStartProcess(string processName, string processPath)
        {
            // Check whather  the program  is running 
            if (!IsProcessRunning(processName))
            {
                Console.WriteLine($"{processName} is not running. Starting the process...");
                StartProcess(processPath);
            }
            else Console.WriteLine($"{processName} is already running.");
        }


        /// <summary>
        /// Enter the program path to start the program
        /// </summary>
        public static void StartProcess(string processPath)
        {
            try
            {
                Process.Start(processPath);
                Console.WriteLine("Process started successfully.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error starting process: {ex.Message}");
            }
        }

        /// <summary>
        /// Check whather the process is running
        /// </summary>
        public static bool IsProcessRunning(string processName)
        {
            Process[] processes = Process.GetProcessesByName(processName);
            return processes.Length > 0;
        }

        /// <summary>
        /// close the process with the specify name.
        /// </summary>
        /// <param name="processName"></param>
        public static void CloseProcess(string processName)
        {
            try
            {
                foreach (var process in Process.GetProcessesByName(processName))
                {
                    process.Kill();
                    process.WaitForExit(); // 等待进程完全退出
                    Application.Current.Shutdown();
                }
            }
            catch (Exception ex)
            {
                // 处理异常
                MessageBox.Show($"无法关闭【{processName}】进程: {ex.Message}");
            }
        }
        /// <summary>
        /// get current process name
        /// </summary>
        public static string GetProgramName()
        {
            // 获取当前执行的程序集
            Assembly assembly = Assembly.GetExecutingAssembly();
            // 获取程序集的名称
            return assembly.GetName().Name;
        }
        #endregion

        #region File save
        /// <summary>
        /// 保存记录
        /// </summary>
        public void WriteDataToFileAsync(string data, int retryCount = 5, int delayMilliseconds = 500)
        {
            //Get the directory where the file located.
            string directoryPath = Path.GetDirectoryName(CurrentPath);

            // if directory exists't ,create directory(include all must directory).
            if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
            {
                Directory.CreateDirectory(directoryPath);
            }

            for (int i = 0; i < retryCount; i++)
            {
                try
                {
                    using (FileStream fs = new FileStream(CurrentPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                    using (StreamWriter writer = new StreamWriter(fs, Encoding.UTF8))
                    {
                         writer.WriteAsync(data);
                    }
                    return;                             // successful writed exit the loop.
                }
                catch (IOException ex)
                {
                    if (i == retryCount - 1)
                    {
                        throw;                          //If the maximum number of retries is reached , a exception is thrown
                    }
                      Task.Delay(delayMilliseconds);    // Wait a while and try again
                }
                catch (Exception ex)
                {
                    throw;                              //other exception is thrown
                }
            }
        }
        #endregion
    }
}
